package com.test45_JUC;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;

/**
 * StampedLock：标记戳锁，乐观锁实现
 */
public class Demo08_StampedLock2 {

    public static void main(String[] args) {
        Account3 account = new Account3("李兴华", 0);
        int[] money = new int[] {110, 230, 10_000};     // 定义要存款的金额
        for (int x = 0; x < 10; x++) {  // 10个读锁
            new Thread(() -> {
                while (true) {
                    System.out.println(account);
                }
            }, "取款线程-"+x).start();
        }
        for (int x = 0; x < 2; x++) {   // 2个写锁
            new Thread(() -> {
                for (int y = 0; y < money.length; y++) {
                    account.save(money[y]);
                }
            }, "存款线程-"+x).start();
        }
    }
}

/**
 * 模拟银行账户
 */
class Account3 {
    private String name;    // 账户的名称
    private int asset;      // 账户的资金
    private StampedLock stampedLock = new StampedLock();    // 标记戳锁

    public Account3 (String name, int asset) {
        this.name = name;
        this.asset = asset;
    }

    // 向银行账户之中进行存款操作
    public void save(int asset) {
        long stamp = this.stampedLock.writeLock();  // 获取写锁，这个时间戳用于解锁
        boolean flag = true;    // 循环控制
        try {
            long writeStamp = this.stampedLock.tryConvertToWriteLock(stamp);    // 将读锁转为写锁
            while (flag) {  // 循环性的尝试转换
                if (writeStamp != 0) {  // 可以获取到读锁转换后的写锁
                    stamp = writeStamp;     // 修改标记戳
                    this.asset += asset;
                    TimeUnit.SECONDS.sleep(1);
                    System.err.printf("【%s】修改银行资产，当前资产为：%10.2f%n", Thread.currentThread().getName(), this.asset / 100.0);
                    flag = false;   // 退出当前循环
                } else {
                    this.stampedLock.unlockRead(stamp);     // 强制性的释放读锁
                    writeStamp = this.stampedLock.writeLock();  // 获取写锁
                    stamp = writeStamp;     // 修改标记戳
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            this.stampedLock.unlockWrite(stamp);    // 解除写锁
        }
    }

    @Override
    public String toString() {
        long stamp = this.stampedLock.tryOptimisticRead();  // 尝试获取乐观锁
        try {
            int current = this.asset;   // 获取当前的金额，这个金额有可能过时
            TimeUnit.MILLISECONDS.sleep(200);
            if (!this.stampedLock.validate(stamp)) {    // 标记戳不正确
                stamp = this.stampedLock.readLock();    // 重新创建一个读锁
                try {
                    current = this.asset;   // 因为有可能数据被修改
                } finally {
                    this.stampedLock.unlockRead(stamp); // 释放读锁
                }
            }
            return String.format("【%s】账户名称：%s、账号余额：%10.2f", Thread.currentThread().getName(), this.name, current / 100.0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}